{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# .env 파일을 읽어서 환경변수로 설정\n",
    "from dotenv import load_dotenv\n",
    "\n",
    "# 토큰 정보로드\n",
    "load_dotenv()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# LangSmith 추적을 설정합니다. https://smith.langchain.com\n",
    "# !pip install -qU langchain-teddynote\n",
    "from langchain_teddynote import logging\n",
    "\n",
    "# 프로젝트 이름을 입력합니다.\n",
    "logging.langsmith(\"CH01-Basic\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 데이터를 효과적으로 전달하는 방법\n",
    "\n",
    "- `RunnablePassthrough` 는 입력을 변경하지 않거나 추가 키를 더하여 전달할 수 있습니다. \n",
    "- `RunnablePassthrough()` 가 단독으로 호출되면, 단순히 입력을 받아 그대로 전달합니다.\n",
    "- `RunnablePassthrough.assign(...)` 방식으로 호출되면, 입력을 받아 assign 함수에 전달된 추가 인수를 추가합니다."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### RunnablePassthrough\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.prompts import PromptTemplate\n",
    "from langchain_openai import ChatOpenAI\n",
    "\n",
    "\n",
    "# prompt 와 llm 을 생성합니다.\n",
    "prompt = PromptTemplate.from_template(\"{num} 의 10배는?\")\n",
    "llm = ChatOpenAI(temperature=0)\n",
    "\n",
    "# chain 을 생성합니다.\n",
    "chain = prompt | llm"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "chain 을 `invoke()` 하여 실행할 때는 입력 데이터의 타입이 딕셔너리여야 합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# chain 을 실행합니다.\n",
    "chain.invoke({\"num\": 5})"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "하지만, langchain 라이브러리가 업데이트 되면서 1개의 변수만 템플릿에 포함하고 있다면, 값만 전달하는 것도 가능합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# chain 을 실행합니다.\n",
    "chain.invoke(5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "아래는 `RunnablePassthrough` 를 사용한 예제입니다.\n",
    "\n",
    "`RunnablePassthrough` 는 `runnable` 객체이며, `runnable` 객체는 `invoke()` 메소드를 사용하여 별도 실행이 가능합니다.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.runnables import RunnablePassthrough\n",
    "\n",
    "# runnable\n",
    "RunnablePassthrough().invoke({\"num\": 10})"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "아래는 `RunnablePassthrough` 로 체인을 구성하는 예제입니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "runnable_chain = {\"num\": RunnablePassthrough()} | prompt | ChatOpenAI()\n",
    "\n",
    "# dict 값이 RunnablePassthrough() 로 변경되었습니다.\n",
    "runnable_chain.invoke(10)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "다음은 `RunnablePassthrough.assign()` 을 사용하는 경우와 비교한 결과입니다.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "RunnablePassthrough().invoke({\"num\": 1})"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`RunnablePassthrough.assign()`\n",
    "\n",
    "- 입력 값으로 들어온 값의 key/value 쌍과 새롭게 할당된 key/value 쌍을 합칩니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 입력 키: num, 할당(assign) 키: new_num\n",
    "(RunnablePassthrough.assign(new_num=lambda x: x[\"num\"] * 3)).invoke({\"num\": 1})"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## RunnableParallel"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.runnables import RunnableParallel\n",
    "\n",
    "# RunnableParallel 인스턴스를 생성합니다. 이 인스턴스는 여러 Runnable 인스턴스를 병렬로 실행할 수 있습니다.\n",
    "runnable = RunnableParallel(\n",
    "    # RunnablePassthrough 인스턴스를 'passed' 키워드 인자로 전달합니다. 이는 입력된 데이터를 그대로 통과시키는 역할을 합니다.\n",
    "    passed=RunnablePassthrough(),\n",
    "    # 'extra' 키워드 인자로 RunnablePassthrough.assign을 사용하여, 'mult' 람다 함수를 할당합니다. 이 함수는 입력된 딕셔너리의 'num' 키에 해당하는 값을 3배로 증가시킵니다.\n",
    "    extra=RunnablePassthrough.assign(mult=lambda x: x[\"num\"] * 3),\n",
    "    # 'modified' 키워드 인자로 람다 함수를 전달합니다. 이 함수는 입력된 딕셔너리의 'num' 키에 해당하는 값에 1을 더합니다.\n",
    "    modified=lambda x: x[\"num\"] + 1,\n",
    ")\n",
    "\n",
    "# runnable 인스턴스에 {'num': 1} 딕셔너리를 입력으로 전달하여 invoke 메소드를 호출합니다.\n",
    "runnable.invoke({\"num\": 1})"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Chain 도 RunnableParallel 적용할 수 있습니다.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "chain1 = (\n",
    "    {\"country\": RunnablePassthrough()}\n",
    "    | PromptTemplate.from_template(\"{country} 의 수도는?\")\n",
    "    | ChatOpenAI()\n",
    ")\n",
    "chain2 = (\n",
    "    {\"country\": RunnablePassthrough()}\n",
    "    | PromptTemplate.from_template(\"{country} 의 면적은?\")\n",
    "    | ChatOpenAI()\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "combined_chain = RunnableParallel(capital=chain1, area=chain2)\n",
    "combined_chain.invoke(\"대한민국\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## RunnableLambda\n",
    "\n",
    "RunnableLambda 를 사용하여 사용자 정의 함수를 맵핑할 수 있습니다.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.output_parsers import StrOutputParser\n",
    "from langchain_core.prompts import PromptTemplate\n",
    "from langchain_openai import ChatOpenAI\n",
    "from datetime import datetime\n",
    "\n",
    "\n",
    "def get_today(a):\n",
    "    # 오늘 날짜를 가져오기\n",
    "    return datetime.today().strftime(\"%b-%d\")\n",
    "\n",
    "\n",
    "# 오늘 날짜를 출력\n",
    "get_today(None)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.runnables import RunnableLambda, RunnablePassthrough\n",
    "\n",
    "# prompt 와 llm 을 생성합니다.\n",
    "prompt = PromptTemplate.from_template(\n",
    "    \"{today} 가 생일인 유명인 {n} 명을 나열하세요. 생년월일을 표기해 주세요.\"\n",
    ")\n",
    "llm = ChatOpenAI(temperature=0, model_name=\"gpt-4.1-mini\")\n",
    "\n",
    "# chain 을 생성합니다.\n",
    "chain = (\n",
    "    {\"today\": RunnableLambda(get_today), \"n\": RunnablePassthrough()}\n",
    "    | prompt\n",
    "    | llm\n",
    "    | StrOutputParser()\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 출력\n",
    "print(chain.invoke(3))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`itemgetter` 를 사용하여 특정 키를 추출합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from operator import itemgetter\n",
    "\n",
    "from langchain_core.prompts import ChatPromptTemplate\n",
    "from langchain_core.runnables import RunnableLambda\n",
    "from langchain_openai import ChatOpenAI\n",
    "\n",
    "\n",
    "# 문장의 길이를 반환하는 함수입니다.\n",
    "def length_function(text):\n",
    "    return len(text)\n",
    "\n",
    "\n",
    "# 두 문장의 길이를 곱한 값을 반환하는 함수입니다.\n",
    "def _multiple_length_function(text1, text2):\n",
    "    return len(text1) * len(text2)\n",
    "\n",
    "\n",
    "# _multiple_length_function 함수를 사용하여 두 문장의 길이를 곱한 값을 반환하는 함수입니다.\n",
    "def multiple_length_function(_dict):\n",
    "    return _multiple_length_function(_dict[\"text1\"], _dict[\"text2\"])\n",
    "\n",
    "\n",
    "prompt = ChatPromptTemplate.from_template(\"{a} + {b} 는 무엇인가요?\")\n",
    "model = ChatOpenAI()\n",
    "\n",
    "chain1 = prompt | model\n",
    "\n",
    "chain = (\n",
    "    {\n",
    "        \"a\": itemgetter(\"word1\") | RunnableLambda(length_function),\n",
    "        \"b\": {\"text1\": itemgetter(\"word1\"), \"text2\": itemgetter(\"word2\")}\n",
    "        | RunnableLambda(multiple_length_function),\n",
    "    }\n",
    "    | prompt\n",
    "    | model\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "chain.invoke({\"word1\": \"hello\", \"word2\": \"world\"})"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "py-test",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
